package controllers

import (
	"encoding/json"
	"fmt"
	"strings"

	"cvevulner/common"
	"cvevulner/models"
	"cvevulner/util"

	"github.com/astaxie/beego"
	"github.com/astaxie/beego/logs"
)

// Operations about Packages

type CveDetailController struct {
	BaseController
}

const issueurl = "https://gitee.com/%s/%s/issues/%s"

func (c *CveDetailController) failed(err error) {
	resp := make(map[string]interface{})
	resp["code"] = -1
	resp["message"] = err.Error()
	resp["count"] = 0
	resp["data"] = nil
	c.response(resp)
}

type respData struct {
	CveNum          string  `json:"cve_num"`
	OwnedComponent  string  `json:"owned_component"`
	Version         string  `json:"version"`
	IssueNum        string  `json:"issue_num"`
	IssueUrl        string  `json:"issue_url"`
	IssueStatus     int8    `json:"issue_status"`
	IssueStatusName string  `json:"issue_status_name"`
	Status          int8    `json:"status"`
	StatusName      string  `json:"status_name"`
	Type            string  `json:"scoring_system"`
	NvdScore        float64 `json:"nvd_score"`
	NvdVector       string  `json:"nvd_vector"`
	OpeneulerScore  float64 `json:"openeuler_score"`
	OpeneulerVector string  `json:"openeuler_vector"`
	Owner           string  `json:"owner"`
	CveUrl          string  `json:"cve_url"`
	Purl            string  `json:"purl"`
}

func (c *CveDetailController) successSbom(data []respData) {
	resp := make(map[string]interface{})
	resp["code"] = 200
	resp["message"] = "success"
	resp["count"] = len(data)
	resp["data"] = data
	c.response(resp)
	return
}

// Get @Title Get cvedetail
// @Description get cvedetail
// @Param	cvenumber		type 	string	true
// @Success 200 {object} models.uploadcve
// @Failure 403 :cvenumber is err
// @router / [get]
func (c *CveDetailController) Get() {
	resp := make(map[string]interface{})

	token := c.GetString("token")
	if token == "" {
		c.noToken(resp)
	} else {
		// Check token
		ok := models.CheckToken(token)
		if !ok {
			c.tokenValidateFailed(resp)
		}
	}
	cveNum := c.GetString("cveNum")
	if cveNum == "" {
		c.paramError(resp)
	}
	cveType, typeError := c.GetInt64("cveType")
	if typeError != nil || cveType == 0 {
		c.paramError(resp)
	}
	// The original data comes from the display of the Chinese Academy of Sciences
	if cveType == 1 {
		v, ouErr := models.GetOriginUpstream(cveNum)
		if ouErr != nil || v.CveId == 0 {
			c.noData(resp, nil)
		}
		resp["body"] = v.ToDetailSummary()
		c.success(resp)
	}

	c.noData(resp, nil)
}

func (c *CveDetailController) DetailList() {
	var resp = make(map[string]interface{})
	currentPage, err := c.GetInt("page_num", 1)
	if err != nil {
		logs.Error("err: ", err, ", page_num: ", currentPage)
		c.paramError(resp)
	}
	pageSize, err := c.GetInt("count_per_page", 100)
	if err != nil {
		logs.Error("err: ", err, ", count_per_page: ", pageSize)
		c.paramError(resp)
	}

	count := models.QueryOriginUpstreamCount()

	list := models.QueryOriginUpstream(currentPage, pageSize)

	var res = struct {
		Total int64                  `json:"total"`
		List  []common.CveOriginData `json:"list"`
	}{}

	l := len(list)

	res.List = make([]common.CveOriginData, 0, l)
	res.Total = count

	size := 40
	p := l / size
	if l%size != 0 {
		p++
	}

	var d = make(chan []common.CveOriginData, p)

	for i := 0; i < p; i++ {
		start := i * size
		end := (i + 1) * size
		if end > l {
			end = l
		}

		go models.CveOriginDetail(list[start:end], d)
	}

	for i := 0; i < cap(d); i++ {
		data := <-d
		res.List = append(res.List, data...)
	}

	resp["body"] = res
	c.success(resp)

}

// Post purl accord get cve detail
func (c *CveDetailController) Post() {
	cveRef := beego.AppConfig.String("cve::cveref")
	var req common.SbomReq
	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
	if err != nil {
		c.failed(err)
	}

	var data []respData
	for _, v := range req.Coordinates {
		purl, purlerr := util.PasePurl(v)
		if purlerr != nil {
			logs.Error(fmt.Sprintf("parse purl failed, purl:%s", v))
			continue
		}
		pkg, pok := purl.GetName()
		version, vok := purl.GetVersion()
		if !pok || !vok {
			logs.Error(fmt.Sprintf("get purl failed, purl:%s", purl))
			continue
		}

		sbom, serr := models.QueryCveIssueForSbom(pkg, version)
		if serr != nil {
			logs.Error(fmt.Sprintf("sbom get issue query failed:%s", err.Error()))
			continue
		}

		for _, template := range sbom {
			data = append(data, respData{
				CveNum:          template.CveNum,
				OwnedComponent:  template.OwnedComponent,
				Version:         template.OwnedVersion,
				IssueNum:        template.IssueNum,
				IssueUrl:        fmt.Sprintf(issueurl, template.Owner, template.Repo, template.IssueNum),
				IssueStatus:     template.Status,
				IssueStatusName: c.statusName(template.Status),
				Status:          template.IssueStatus,
				StatusName:      c.issueStatusName(template.IssueStatus),
				Type:            c.scoreType(template.ScoreType),
				NvdScore:        template.NVDScore,
				NvdVector:       template.NVDVector,
				OpeneulerScore:  template.OpenEulerScore,
				OpeneulerVector: template.OpenEulerVector,
				Owner:           template.Owner,
				CveUrl:          cveRef + template.CveNum,
				Purl:            purl.Purl(),
			})
		}
	}

	c.successSbom(data)
}

// Patch get patch info with cve
func (c *CveDetailController) Patch() {
	cveNum := c.GetString("cve_num")
	patches, err := models.QueryCveOriginPatchInfo(cveNum)
	if err != nil {
		c.failed(err)
	}

	list := make([]string, len(patches))
	for _, v := range patches {
		list = append(list, v.FixPatch)
	}

	var resp = make(map[string]interface{})
	resp["body"] = list

	c.success(resp)
}

func (c *CveDetailController) issueStatusName(i int8) string {
	//1:待分析；2：已正常关闭；3：已分析，待修复；4：已修复；5：已发布；6：已异常关闭"
	switch i {
	case 1:
		return "待分析"
	case 2:
		return "已正常关闭"
	case 3:
		return "已分析,待修复"
	case 4:
		return "已修复"
	case 5:
		return "已发布"
	case 6:
		return "已异常关闭"
	default:
		return "unknown"
	}
}

func (c *CveDetailController) statusName(i int8) string {
	//1:待办的；2：进行中；3：已完成；4：已拒绝；5： 已挂起; 6:已删除"`
	switch i {
	case 1:
		return "待办的"
	case 2:
		return "进行中"
	case 3:
		return "已完成"
	case 4:
		return "已拒绝"
	case 5:
		return "已挂起"
	case 6:
		return "已删除"
	default:
		return "unknown"
	}
}

func (c *CveDetailController) scoreType(typ string) string {
	switch strings.ToLower(typ) {
	case "v2":
		return "CVSS2"
	case "v3":
		return "CVSS3"
	default:
		return "CVSS2"
	}
}
